from ctypes import *
from datetime import datetime

from ys_python.common.base.config import get_config_value
from ys_python.common.base.logger import get_app_logger
from ys_python.common.base.single_ton import SingleTon


@SingleTon
class NIDAQ:
    """NIDAQ DLL 封装类
    全局唯一

    """
    # //*** Value for the Terminal Config parameter of
    # DAQmxCreateAIVoltageChan, DAQmxCreateAICurrentChan and DAQmxCreateAIVoltageChanWithExcit ***
    Val_Cfg_Default = c_int32(-1)

    # Values for DAQmx_AI_TermCfg
    # Value set InputTermCfg
    Val_RSE = c_int32(10083)
    Val_NRSE = c_int32(10078)
    Val_Diff = c_int32(10106)
    Val_PseudoDiff = c_int32(12529)

    Val_Volts = c_int32(10348)

    Val_Rising = c_int32(10280)
    Val_Falling = c_int32(10171)

    Val_FiniteSamps = c_int32(10178)
    Val_ContSamps = c_int32(10123)
    Val_HWTimedSinglePoint = c_int32(12522)

    Val_GroupByChannel = c_bool(False)
    Val_GroupByScanNumber = c_bool(True)

    def __init__(self):
        self._dll = CDLL(get_config_value("$.ys_python.nidaq.dll"))
        self.logger = get_app_logger("ni-daq")

    def hello(self):
        self._dll.NIDAQ_Hello()

    # region 任务

    def create_task(self, task_name: c_char_p, task_handle: pointer):
        return self._dll.NIDAQ_CreateTask(task_name, task_handle)

    def start_task(self, task_handle: c_void_p):
        return self._dll.NIDAQ_StartTask(task_handle)

    def stop_task(self, task_handle: c_void_p):
        return self._dll.NIDAQ_StopTask(task_handle)

    def clear_task(self, task_handle: c_void_p) -> c_int32:
        return self._dll.NIDAQ_ClearTask(task_handle)

    # endregion

    # region 通道
    def create_ai_voltage_chan(self, task_handle: c_void_p, physical_channel: c_char_p,
                               name_to_assign_to_channel: c_char_p, terminal_config: c_int32, min_val: c_double,
                               max_val: c_double, units: c_int32, custom_scale_name: c_char_p):
        return self._dll.NIDAQ_CreateAIVoltageChan(task_handle, physical_channel, name_to_assign_to_channel,
                                                   terminal_config, min_val, max_val, units, custom_scale_name)

    def create_ao_voltage_chan(self, task_handle: c_void_p, physical_channel: c_char_p,
                               name_to_assign_to_channel: c_char_p, min_val: c_double,
                               max_val: c_double, units: c_int32, custom_scale_name: c_char_p):
        return self._dll.NIDAQ_CreateAOVoltageChan(task_handle, physical_channel, name_to_assign_to_channel,
                                                   min_val, max_val, units, custom_scale_name)

    # endregion

    # region 读
    def read_analog_f_64(self, task_handle: c_void_p, num_samps_per_chan: c_int32, timeout: c_double, fill_mode: c_bool,
                         read_array: pointer, array_size_in_samps: c_uint32, samps_per_chan_read: pointer,
                         reserved: c_void_p):
        return self._dll.NIDAQ_ReadAnalogF64(task_handle, num_samps_per_chan, timeout, fill_mode, read_array,
                                             array_size_in_samps, samps_per_chan_read, reserved);

    def read_binary_i16(self, task_handle: c_void_p, num_samps_per_chan: c_int32, timeout: c_double, fill_mode: c_bool,
                        read_array: pointer, array_size_in_samps: c_uint32, samps_per_chan_read: pointer,
                        reserved: c_void_p):
        return self._dll.NIDAQ_ReadBinaryI16(task_handle, num_samps_per_chan, timeout, fill_mode, read_array,
                                             array_size_in_samps, samps_per_chan_read, reserved);

    def read_binary_i32(self, task_handle: c_void_p, num_samps_per_chan: c_int32, timeout: c_double, fill_mode: c_bool,
                        read_array: pointer, array_size_in_samps: c_uint32, samps_per_chan_read: pointer,
                        reserved: c_void_p):
        return self._dll.NIDAQ_ReadBinaryI32(task_handle, num_samps_per_chan, timeout, fill_mode, read_array,
                                             array_size_in_samps, samps_per_chan_read, reserved);

    # endregion

    # region 写
    def write_binary_i16(self, task_handle: c_void_p, num_samps_per_chan: c_int32, auto_start: c_bool,
                         timeout: c_double, data_layout: c_bool, write_array: pointer, samps_per_chan_written: pointer,
                         reserved: c_void_p):
        return self._dll.NIDAQ_WriteBinaryI16(task_handle, num_samps_per_chan, auto_start, timeout,
                                              data_layout, write_array, samps_per_chan_written, reserved)

    def write_binary_i32(self, task_handle: c_void_p, num_samps_per_chan: c_int32, auto_start: c_bool,
                         timeout: c_double, data_layout: c_bool, write_array: pointer, samps_per_chan_written: pointer,
                         reserved: c_void_p):
        return self._dll.NIDAQ_WriteBinaryI32(task_handle, num_samps_per_chan, auto_start, timeout,
                                              data_layout, write_array, samps_per_chan_written, reserved)

    # endregion

    # region 时钟
    def cfg_samp_clk_timing(self, task_handle: c_void_p, source: c_char_p, rate: c_double, active_edge: c_int32,
                            sample_mode: c_int32, samps_per_chan: c_uint64):
        return self._dll.NIDAQ_CfgSampClkTiming(task_handle, source, rate, active_edge,
                                                sample_mode, samps_per_chan)

    # endregion

    def filed(self, error: c_int32, show_err=True) -> c_bool:
        if self._dll.NIDAQ_Failed(error) and show_err:
            buf = create_string_buffer(b'', 1024)
            self.get_error_string(error, pointer(buf), c_uint32(sizeof(buf)))
            self.logger.error(buf.value)
        return self._dll.NIDAQ_Failed(error)

    def get_error_string(self, err_code: c_int32, error_str: pointer, buffer_size: c_uint32):
        return self._dll.NIDAQ_GetErrorString(err_code, error_str, buffer_size)

    def self_test_device(self, device_name):
        return self._dll.NIDAQ_SelfTestDevice(device_name)

    def reset_device(self, device_name):
        return self._dll.NIDAQ_ResetDevice(device_name)


if __name__ == '__main__':
    logger = get_app_logger("ni-daq")
    nidaq: NIDAQ = NIDAQ()
    # ai 任务 创建了 AI 的通道数
    ai_handler = c_void_p(0)
    if nidaq.filed(nidaq.self_test_device(c_char_p(b"cDAQ1Mod3"))):
        logger.info("自测试失败！")
        exit(-1)
    else:
        logger.info("自测试成功")

    if nidaq.filed(nidaq.create_task(c_char_p(b"Task01"), pointer(ai_handler))):
        logger.info("创建任务错误")
    err = nidaq.create_ai_voltage_chan(ai_handler, c_char_p(bytes("cDAQ1Mod3/ai1", "gbk")), c_char_p(None),
                                       nidaq.Val_Cfg_Default,
                                       c_double(-10), c_double(10), nidaq.Val_Volts, c_char_p(None))
    if nidaq.filed(err):
        logger.info("通道创建失败了!")
    err = nidaq.create_ai_voltage_chan(ai_handler, c_char_p(bytes("cDAQ1Mod3/ai3", "gbk")), c_char_p(None),
                                       nidaq.Val_Cfg_Default,
                                       c_double(-10), c_double(10), nidaq.Val_Volts, c_char_p(None))
    if nidaq.filed(err):
        logger.info("通道创建失败了!")

    if nidaq.filed(
            nidaq.cfg_samp_clk_timing(ai_handler, c_char_p(None), c_double(2500), nidaq.Val_Rising,
                                      nidaq.Val_ContSamps,
                                      c_uint64(5000))):
        logger.info("创建时钟失败!")

    # ao 任务
    ao_handler = c_void_p(0)
    if nidaq.filed(nidaq.create_task(c_char_p(b"Task02"), pointer(ao_handler))):
        logger.info("创建任务错误")
    err = nidaq.create_ao_voltage_chan(ao_handler, c_char_p(bytes("cDAQ1Mod5/ao0", "gbk")), c_char_p(None),
                                       c_double(-10), c_double(10), nidaq.Val_Volts, c_char_p(None))
    if nidaq.filed(err):
        logger.info("cDAQ1Mod4/ao0 通道创建失败了!")
    err = nidaq.create_ao_voltage_chan(ao_handler, c_char_p(bytes("cDAQ1Mod5/ao2", "gbk")), c_char_p(None),
                                       c_double(-5), c_double(5), nidaq.Val_Volts, c_char_p(None))
    if nidaq.filed(err):
        logger.info("cDAQ1Mod4/ao2  通道创建失败了!")

    if nidaq.filed(
            nidaq.cfg_samp_clk_timing(ao_handler, c_char_p(None), c_double(50), nidaq.Val_Rising, nidaq.Val_ContSamps,
                                      c_uint64(5000))):
        logger.info("创建时钟失败!")

    write_array = (c_int16 * 1000)()
    for i in range(len(write_array)):
        if i % 2 == 0:
            write_array[i] = int(32768 / 2)
        else:
            write_array[i] = 32767
    per2 = c_int32(0)
    nidaq.write_binary_i16(ao_handler, c_int32(int(len(write_array) / 2)), c_bool(True), c_double(-1),
                           nidaq.Val_GroupByScanNumber,
                           pointer(write_array),
                           pointer(per2), c_void_p(None))
    print(per2)

    nidaq.start_task(ao_handler)
    nidaq.start_task(ai_handler)
    i = 0
    while True:
        read_array = (c_int32 * 10000)()
        per = c_int32(0)
        err = nidaq.read_binary_i32(ai_handler, c_int32(5000), c_double(-1), nidaq.Val_GroupByScanNumber,
                                    pointer(read_array),
                                    c_uint32(sizeof(read_array)),
                                    pointer(per), c_void_p(None))
        print(datetime.now())
        if not nidaq.filed(err):
            print(read_array[0:10])
            print(per)

        i += 1

    # nidaq.stop_task(ao_handler)
    nidaq.stop_task(ai_handler)
    if nidaq.filed(nidaq.clear_task(ai_handler)):
        logger.info("清理任务失败")
